(declaim (optimize (debug 3) (safety 3)))

;;====================================================================
;; gnuplot.lisp
;;
;; This package, :roll/gnuplot, procides good interface to
;; gnuplot.
;; It supports draw function or points int 2D/3D.
;;
;;                               written by R.Ueda(garaemon@gmail.net)
;;====================================================================

(defpackage :roll/gnuplot
  (:use #:common-lisp #:iterate)
  (:export #:open-gnuplot
           #:close-gnuplot
           #:plot-function
           #:plot-points)
  (:documentation
   "roll/gnuplot is a wrapper package of cgn.")
  )

(in-package :roll/gnuplot)

(defvar *gnuplot* nil
  "instance of <gnuplot>.
   <gnuplot> has the input/output streams to gnuplot process.")

(roll/util:defclass* <gnuplot>
    ()
  ((stream nil)
   (data nil)))

(defun open-gnuplot (&optional (path "gnuplot"))
  "open gnuplot process. You have to call this function before plotting.
   If you doesn't have gnuplot in your PATH,
   you should set gnuplot path to the \"path\" parameter."
  (setf *gnuplot* (make-instance '<gnuplot>
                      :stream (sb-ext:run-program path nil :input :stream :output :stream :wait nil :search t)))
  (unless *gnuplot*
    (error "Cannot create ~A process" path))
  *gnuplot*)

(defun close-gnuplot ()
  "close gnuplot process."
  (if *gnuplot*
      (close (stream-of *gnuplot*)))
  t)

;;(defgeneric format-to-gnuplot (gnuplot str))
;;  "input str to the process gnuplot.")

(defmethod format-to-gnuplot ((gnuplot <gnuplot>) str)
  (format (cl-user::process-input (stream-of gnuplot)) "~A" str)
  (force-output (cl-user::process-input (stream-of gnuplot)))
  str)

(defun plot-function (func-str &key (dimension 2)
                                    (clear t)
                                    (range nil))
  "plotting a fuction"
  (if range
      (format-to-gnuplot *gnuplot* (format nil "[~A:~A]" (car range) (cadr range))))
  (cond ((= dimension 2)
         (format-to-gnuplot *gnuplot* (format nil "plot ~A" func-str)))
        ((= dimension 3)
         (format-to-gnuplot *gnuplot* (format nil "splot ~A" func-str)))
        (t
         (error "diemnsion must be 2 or 3."))
        ))
  
(defun plot-points (xs ys &key (xrange nil) (yrange nil) (titles nil))
  (format (cl-user::process-input (stream-of *gnuplot*)) "plot ")
  (if xrange
      (format (cl-user::process-input (stream-of *gnuplot*))
              (format nil " [~A:~A] " (car xrange) (cadr xrange)))
      (format (cl-user::process-input (stream-of *gnuplot*))
              " [] "))
  (if yrange
      (format (cl-user::process-input (stream-of *gnuplot*))
              (format nil " [~A:~A] " (car yrange) (cadr yrange))))
  (format (cl-user::process-input (stream-of *gnuplot*))
          " '-' ")
;;   (if title
;;       (format *gnuplot* (format nil " title \"~A\"" (pop titles))))
  (format (cl-user::process-input (stream-of *gnuplot*))
          (format nil " title \"~A\" ~%" "x"))
  ;;(dolist (v
  (iterate (for x in xs)
           (for y in ys)
           (format (cl-user::process-input (stream-of *gnuplot*))
                   (format nil "~A ~A~%" x y))
           )
  (format (cl-user::process-input (stream-of *gnuplot*))
          "e~%")
  (force-output (cl-user::process-input (stream-of *gnuplot*)))
  )

